
package com.kweative.type;

import com.kweative.util.ReflectUtil;
import com.kweative.util.StringUtil;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Time;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author henrik
 */
public class Node extends Object {
    
	private String name = null;
	private Object value = null;
	private HashMap<String,Object> attributes = new HashMap<String, Object>();
	private LinkedList<Node> children = new LinkedList<Node>();
	private Node parent = null;
    private Type type = Type.AUTO;

    public Node() {
        
    }
	public Node(String name) {
		this.name = name;
	}
    public Node(String name,Type type) {
        this(name);
        this.type = type;
    }
    public Node(Type type) {
        this.type = type;
    }

	public Node makeChild(String name) {
		Node child = new Node(name);
		addChild(child);
		return child;
	}
    public Node makeChild(String name,Object value) {
        return makeChild(name).setValue(value);
    }

	public Node addChild(Node child) {
		children.addLast(child);
		child.setParent(this);
		return this;
	}
    public boolean hasChildren() {
        return children.size() > 0;
    }
	public Node getChild(String name) {
		for(Node child : children) {
			if (child.getName().equalsIgnoreCase(name)) {
				return child;
			}
		}
		return null;
	}
    public boolean hasChild(String name) {
        return getChild(name) != null;
    }
	public Node getChild(int index) {
		if (children.size() > (index+1)) {
			return children.get(index);
		}
		return null;
	}
	public Node setAttribute(String name,Object value) {
		attributes.put(name, value);
		return this;
	}
	public Object getAttribute(String name) {
		return attributes.get(name);
	}
    public boolean hasAttribute(String name) {
        return attributes.containsKey(name);
    }
	public LinkedList<Node> children() {
		return children;
	}
	public HashMap<String,Object> attributes() {
		return attributes;
	}
    public boolean hasAttributes() {
        return attributes().size() > 0;
    }
	public String getName() {
		return name;
	}

	public Node setName(String name) {
		this.name = name;
        return this;
	}

	public Node getParent() {
		return parent;
	}

	public Node setParent(Node parent) {
		this.parent = parent;
        return this;
	}

	public Object getValue() {
		return value;
	}

	public Node setValue(Object value) {
		this.value = value;
        return this;
	}
	public Node getFirst() {
		return children.getFirst();
	}
	public Node getLast() {
		return children.getLast();
	}
	public boolean isEmpty() {
		return this.children.size() == 0;
	}

    public Type getType() {
        if (type == Type.AUTO) {
            if (isSimple())
                return (isNameValue()) ? Type.NAMEVALUE : (this.getValue() == null) ? Type.SIMPLE : Type.VALUE;
            return (isArray()) ? Type.ARRAY : Type.COMPLEX;
        }
        return type;
    }

    public Node setType(Type type) {
        this.type = type;
        return this;
    }

    @Override
    public String toString() {
        return (String) getValue();
    }
    public boolean isSimple() {
        return type == Type.SIMPLE || type == Type.AUTO && !hasChildren() && attributes().size() == 0;
    }
    public boolean isValue() {
        return type == Type.VALUE || type == Type.AUTO && isSimple() && getName() == null;
    }
    public boolean isNameValue() {
        return type == Type.NAMEVALUE || type == Type.AUTO &&  isSimple() && getName() != null;
    }
    public boolean isComplex() {
        return type == Type.COMPLEX || type == Type.AUTO && !isSimple();
    }
    public boolean isArray() {
        return type == Type.ARRAY || type == Type.AUTO && hasChildren() && !hasAttributes() && getValue() == null;
    }

    public enum Type {
        AUTO,
        SIMPLE,
        NAMEVALUE,
        COMPLEX,
        ARRAY,
        VALUE
    }
    
    public static Node fromObject(Object obj) {
        Node node = new Node();
        if (ReflectUtil.isPrimitive(obj.getClass())) {
            node.setType(Type.VALUE);
            node.setValue(obj);
            return node;
        }
        if (obj instanceof Date) {
            node.setType(Type.VALUE);
            node.setValue(obj.toString());
            return node;
        }
        if (obj instanceof ICanDoNode) {
            return ((ICanDoNode)obj).toNode();
        }
        String name = obj.getClass().getSimpleName();
        node.setType(Type.COMPLEX);
        node.setName(name);
        boolean isMap = false;
        if (obj instanceof Map)
            isMap = true;
        else if (obj instanceof Collection || obj.getClass().isArray())
            node.setType(Type.ARRAY);

        if (!isMap && node.isComplex()) {
            Field[] fields = obj.getClass().getDeclaredFields();
            for(Field field:fields) {
                String fieldName = field.getName();
                Object fieldValue = null;
                if (field.isAccessible()) {
                    try {
                        fieldValue = field.get(obj);
                    } catch (Exception ex) {
                        Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } else {
                    try {
                        Method getters[] = ReflectUtil.getMethod(obj.getClass(),"get" + field.getName());
                        if (getters.length > 0)
                            fieldValue = getters[0].invoke(obj);
                    } catch (Exception ex) {
                        Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                if (fieldValue != null) {
                    if (fieldValue.getClass().isPrimitive())
                        node.setAttribute(fieldName,fieldValue);
                    else {
                        Node child = fromObject(fieldValue);
                        child.setName(fieldName);
                        node.addChild(child);
                    }
                }
            }
        }
        if (node.isArray()) {
            if (obj.getClass().isArray()) {
                Object objects[] = (Object[]) obj;
                for (Object object:objects) {
                    Node child = fromObject(object);
                    if (child != null)
                        node.addChild(child);
                }
            } else {
                Collection c = (Collection) obj;
                for (Iterator it = c.iterator(); it.hasNext();) {
                    Object object = it.next();
                    Node child = fromObject(object);
                    if (child != null)
                        node.addChild(child);
                }
            }
        }

        return node;
    }
    public <M extends Map> M toMap(Class<M> clazz) {
        M instance;
        try {
            instance = clazz.newInstance();
        } catch (Exception ex) {
            Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        for(Node child: children()) {
            String fieldName = child.getName();
            Object fieldValue = child.toObject(clazz.getTypeParameters()[1].getGenericDeclaration());
            instance.put(fieldName,fieldValue);
        }
        return instance;
    }
    public <C extends Collection> C toCollection(Class<C> clazz) {
        C instance;
        try {
            instance = clazz.newInstance();
        } catch (Exception ex) {
            Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        for(Node child: children()) {
            Object fieldValue = child.toObject(clazz.getTypeParameters()[1].getGenericDeclaration());
            instance.add(fieldValue);
        }
        return instance;
    }
    public Object[] toArray(Class clazz) {
        Object[] instance = new Object[children().size()];
        int i = 0;
        for(Node child: children()) {
            Object fieldValue = child.toObject(clazz.getComponentType());
            instance[i] = fieldValue;
            i++;
        }
        return instance;
    }
    public <R> R toObject(Class<R> clazz) {
        
        if (ReflectUtil.isPrimitive(clazz)) {
            return (R) getValue();
        }
        if (getValue() != null && clazz.isAssignableFrom(getValue().getClass()))
            return (R) getValue();
        
        R instance;
        try {
            instance = clazz.newInstance();
        } catch (Exception ex) {
            Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }

        if (instance instanceof Map)
            return (R)toMap((Class)clazz);
        else if (instance instanceof Collection)
            return (R)toCollection((Class)clazz);
        else if (clazz.isArray())
            return (R)toArray((Class)clazz);

        for(Node child: children()) {
            String fieldName = child.getName();
            Field f = null;
            Object fieldValue = null;
            try {
                f = clazz.getField(fieldName);
                fieldValue = child.toObject(f.getType());
            } catch (Exception ex) {
                Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex);
                continue;
            }
            try {
                f.set(instance,fieldValue);
            } catch (Exception ex) {
                String methodName = "set" + StringUtil.firstToUpper(fieldName);
                try {
                    Method method = clazz.getMethod(methodName, f.getType());
                    method.invoke(instance, fieldValue);
                } catch (Exception ex1) {
                    Logger.getLogger(Node.class.getName()).log(Level.SEVERE, null, ex1);
                }
            }
        }

        return instance;
    }
}
